<template>
    <div>
        <h3>大文件上传模拟</h3>
        <el-upload action="#" :http-request="uploadFile">
            <el-button type="danger"><el-icon><Plus></Plus></el-icon>选择文件</el-button>
        </el-upload>
        <el-progress :percentage="fileItem.uploadProgress" v-if="fileItem.uploadProgress > 0"></el-progress>
    </div>
</template>
<script setup>
console.log(import.meta.env.VITE_APP_SERVER_URL);

import MainThreadWorker from "@/worker/MainThreadWorker.js";
import { reactive, ref } from "vue";

import { uploaderFileApi, unionFileApi,queryUploadFileApi } from "@/api/upload.js";
import { ElMessage } from "element-plus";
import axios from "axios";

//用来存放所有上传文件的对象信息，key可以用一个id来表示，
let fileUid = 0;
//当前存储文件的对象信息
const fileItem = reactive({
    file: null,
    status: 0,
    uploadProgress: 0,
    uploadSize: 0,
    md5: null,
    uploadedList:{},//已经上传过的对象列表
});
const uid = 0;
let precent = ref(0);
let main_thread = null;

const uploadFile = (data) => {
    console.log(data);
    let file = data.file;
    console.log(file, "上传文件信息");
    //实例化主线程的WebWorker的对象
    main_thread = new MainThreadWorker({ uid: fileUid++ });
    //开始向子线程发送消息，传递方法等信息，id在主线程中自主处理，大文件切片生成md5，异步处理减少阻塞
    main_thread.postMessage({
        actionType: "computedMD5",
        payload: {
            file: file,//传递文件的基本信息内容
        }
    })
    //如果一次链接可能子线程返回多个的话，就不使用then了
    main_thread.worker.onmessage = (e) => {
        console.log(e.data, "子线程返回的数据信息", file);
        let { response } = e.data;
        let { md5 } = response;
        //如果有md5说明文件切片解析完了，切片生成了md5的基本信息.
        if (md5) {
            fileItem.file = file;
            fileItem.md5 = md5;
            // sendFileToServer(file, 0, md5);
            sendFileToServer(fileUid, 0)
        }
        return;
    }
    //出问题关闭子线程
    main_thread.worker.onerror = (e) => {
        main_thread.worker.terminate()  //出错后关闭子线程
    }
}

//尝试向后端传递文件信息的捏偶然那个
const sendFileToServer = async (fileUid, fromChunkIndex) => {

    //直接停止执行逻辑
    if(fromChunkIndex == null){
        return ;
    }
    //获取对象信息的内容
    if (!fileItem) return;
    let queryRes = await queryUploadFileApi(fileItem.md5);
    //暂停上传这块，秒传这块先暂时不做了
    if(queryRes.data && queryRes.data.fileStatus == 4500){
         ElMessage.success("实现秒传效果");
        fileItem.uploadProgress = 100;
        return;
    }
    //尝试一个一个向后端传递文件信息 
    let chunkSize = 1024 * 1024;
    //开始切片的基本信息内容
    let chunkIndex = fromChunkIndex || 0;
    let file = fileItem.file;
    let fileSize = file.size;
    //开启分片上传的基本操作
    let chunks = Math.ceil(fileSize / chunkSize);//切片个数
    //定义任务池的内容
    const taskPool = [];
    const maxTask = 6;//最多异步处理任务数量

    //从本地获取一下存储的数据
    let uploadedList = window.sessionStorage.getItem(fileItem.md5) && JSON.parse(window.sessionStorage.getItem(fileItem.md5));
    //循环处理基本信息
    for (let i = chunkIndex; i < chunks; i++) {
        //处理删除或者暂定记录的逻辑这块先空着
        //开始操作计算信息
        let start = chunkSize * i;
        let end = start + chunkSize > fileSize ? fileSize : start + chunkSize;
        let chunkFile = file.slice(start, end);
        let task = null;
        let retries = 0;//重试次数

        //判断如果当前对象中记录该切片已经存在，前端处理一下
        if(uploadedList && uploadedList.hasOwnProperty(i)){
            if(uploadedList[i].fileStatus == 4500){
                fileItem.uploadSize = fileItem.uploadSize + chunkFile.size;
                fileItem.uploadProgress = Math.floor((fileItem.uploadSize / fileSize) * 100);
                continue;//继续下一个循环，跳过接口调用
            }
        }
        
        //开始执行上传的操作
        let doRquest = () => {
            task = uploaderFileApi({ file: chunkFile, fileMd5: fileItem.md5, chunkIndex: i, chunks });

            task.then(res => {
                if (res.code == 200) {
                    fileItem.uploadSize = fileItem.uploadSize + chunkFile.size;
                    fileItem.uploadProgress = Math.floor((fileItem.uploadSize / fileSize) * 100);
                    // fileItem.status = res.data.fileStatus;
                }

                if(res.data){
                    //每次请求发送出去，并且成功之后，我们把上传成功的切片状态记录一下
                    let {chunkIndex} = res.data;
                    fileItem.uploadedList[chunkIndex] = res.data;
                    //内容存储在本地存储中
                    window.sessionStorage.setItem(fileItem.md5,JSON.stringify(fileItem.uploadedList));
                }
                
                taskPool.splice(taskPool.findIndex(item => item === task), 1);//删除已经完成的分片请求任务
            }).catch(error => {
                if (error.code == "ECONNABORTED") {
                    console.error("请求超时请重试")
                    if (retries < 4) {
                        retries++;
                        console.log(`请求超时，进行第${retries}次重试`)
                        doRquest();
                    } else {
                        console.log("次数超限")
                    }
                }

                // console.error("请求出错,",error.message);
            })
        }
        doRquest();
        //上传的任务添加到任务池中去
        taskPool.push(task);
        //超过最大限制的任务总数，使用race方法结束第一个完成的任务，从而避免阻塞代码，限制最大请求数
        if (taskPool.length >= maxTask) {
            console.log("测试次数", i - maxTask);
            await Promise.race(taskPool);
        }

    }
    console.log(taskPool, "测试池子");
    await Promise.all(taskPool);//把剩余的分片请求发送等待所有分片完成任务

    //文件上传进度100%以后开始请求合并合并文件的操作
    if (fileItem.uploadProgress == 100) {
        console.log("文件上传完毕,执行合并")
        await unionFileApi({ fileName: file.name, fileMd5: fileItem.md5 });//发送信息，请求合并操作
    }
}
</script>